package edu.umassd.raddacl.dataset;

import java.util.Iterator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import edu.umassd.raddacl.Cluster;
import edu.umassd.raddacl.DataSet;
import edu.umassd.raddacl.Observation;

/**
 * Provides a collection of observations for the cluster interface.
 * 
 * @author Dan Avila
 * 
 */
@Component("defaultCluster")
@Scope("prototype")
public class DefaultCluster implements Cluster
{
	@Autowired
	private DefaultFactory fac;

	private Cluster parent;
	private DataSet dataSet;
	private Split split;

	@Override
	public Iterable<Cluster> split()
	{
		return new SplitIterator();
	}

	private class SplitIterator implements Iterable<Cluster>, Iterator<Cluster>
	{
		private Cluster currentCluster;

		private int iteration;

		public SplitIterator()
		{
			iteration = (int) Math.pow(2, dataSet.getNumberOfAttributes()) - 1;

			createNextCluster();
		}

		@Override
		public Iterator<Cluster> iterator()
		{
			return this;
		}

		@Override
		public boolean hasNext()
		{
			return currentCluster != null;
		}

		@Override
		public Cluster next()
		{
			try
			{
				return currentCluster;
			}
			finally
			{
				createNextCluster();
			}
		}

		@Override
		public void remove()
		{
			throw new UnsupportedOperationException(
					"You cannot remove clusters from a split.");
		}

		private void createNextCluster()
		{
			if (iteration <= 0)
			{
				this.currentCluster = null;
				return;
			}

			Split split = fac.createSplit();
			split.setCluster(DefaultCluster.this);
			split.setIteration(iteration);

			iteration--;

			this.currentCluster = fac.createCluster(DefaultCluster.this, split);

			if (this.currentCluster == null)
			{
				createNextCluster();
			}
		}
	}

	private class ObservationIterator implements Iterator<Observation>
	{
		private Iterator<Observation> obs = dataSet.iterator();
		private Observation next;

		public ObservationIterator()
		{
			next = getNext();
		}

		@Override
		public boolean hasNext()
		{
			return next != null;
		}

		@Override
		public Observation next()
		{
			try
			{
				return next;
			}
			finally
			{
				next = getNext();
			}
		}

		private Observation getNext()
		{
			while (obs.hasNext())
			{
				Observation o = obs.next();

				if (split.isValidObservation(o))
				{
					return o;
				}
			}

			return null;
		}

		@Override
		public void remove()
		{
			throw new UnsupportedOperationException("");
		}
	};

	@Override
	public Cluster getParent()
	{
		return parent;
	}

	void populate(DataSet set)
	{
		this.dataSet = set;
	}

	void populate(DefaultCluster parent, Split split)
	{
		this.parent = parent;
		this.dataSet = parent.dataSet;
		this.split = split;

		if (iterator().next() == null)
		{
			throw new IllegalArgumentException(
					"Cluster must contain at least 1 element.");
		}
	}

	@Override
	public String toString()
	{
		StringBuilder builder = new StringBuilder("{");
		builder.append(System.getProperty("line.separator"));

		for (Observation obs : this)
		{
			builder.append(obs);
			builder.append(System.getProperty("line.separator"));
		}

		builder.append("}");

		return builder.toString();
	}

	@Override
	public Iterator<Observation> iterator()
	{
		if (split == null)
		{
			return dataSet.iterator();
		}
		else
		{
			return new ObservationIterator();
		}
	}
}
